#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define CNAME "a_back"
#define RED_BASE 0xC001A000 
#define BLUE_BASE 0xC001B000
#define GREEN_BASE 0xC001E000
#define BEEP_BASE 0xC001C000

struct class *class;
struct device *dev;
struct cdev *cdev;

unsigned int *red_base = NULL;
unsigned int *blue_base = NULL;
unsigned int *green_base = NULL;
unsigned int *beep_base = NULL;

int major=0,minor=0;
int ret;
const int count =1;
char kbuf[4];

int drv_open (struct inode *inode, struct file *file)
{
	printk("drv_open\n");
	return 0;
}

ssize_t drv_write (struct file *file, const char __user * ubuf, size_t size, loff_t * offs)
{
	printk("drv_write\n");
	if(size>sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_from_user(kbuf,ubuf,size);
	if(ret)
		{
			printk("copy to user faild\n");
			return -EINVAL;
		}
#if 1
	if(kbuf[0]==1)
	{
		*red_base |= (1<<28);
		*blue_base &= ~(1<<12); 
		*green_base &= ~(1<<13); 
	}
	else
	{
		*red_base &= ~(1<<28); 
	}
	if(kbuf[1]==1)
	{
		*blue_base |= (1<<12);
		*red_base &= ~(1<<28); 
		*green_base &= ~(1<<13); 
	}
	else
	{
		*blue_base &= ~(1<<12); 
	}
	if(kbuf[2]==1)
	{
		*green_base |= (1<<13);
		*red_base &= ~(1<<28); 
		*blue_base &= ~(1<<12); 
	}
	else
	{
		*green_base &= ~(1<<13); 
	}
	if(kbuf[3]==1)
	{
		*beep_base |= (1<<14);
		*green_base &= ~(1<<13);
		*red_base &= ~(1<<28); 
		*blue_base &= ~(1<<12); 
	}
	else
	{
		*beep_base &= ~(1<<14); 
	}

#endif


	return 0;
}

ssize_t drv_read (struct file *file, char __user * ubuf, size_t size, loff_t * offs)
{
	printk("drv_read\n");
	if(size>sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_to_user(ubuf,kbuf,size);
	if(ret)
		{
			printk("copy to user faild\n");
			return -EINVAL;
		}
	return 0;
}

int drv_close (struct inode *inode, struct file *file)
{
	printk("drv_close\n");
	return 0;
}


const struct file_operations fops = {
    .open = drv_open,
    .write = drv_write,
    .read = drv_read,
    .release = drv_close,
};


static int __init drv_init(void){
    dev_t dem;
    cdev = cdev_alloc();
    if(cdev==NULL){
        printk("alloc memory faild\n");
        ret = -ENOMEM;
        goto ERR_STEP0;
    }
    cdev_init(cdev,&fops);

    ret = alloc_chrdev_region(&devm,minor,count,CNAME);
    if(ret)
    {
        printk("alloc register number failed\n");
        goto ERR_STEP1;
    }
    major = MAJOR(devm);
    minor = MINOR(devm);

    ret = cdev_add(cdev,MKDEV(major,mino),count);
    if(ret){
        printk("char device regist failed\n");
        goto ERR_STEP2;
    }

    cls = class_create(THIS_MODULE,CNAME);
    if(IS_ERROR(cls)){
        printk("class create failed\n");
        ret = PTR_ERROR(cls);
        goto ERR_STEP3;
    }

    dev = device_create(cls,NULL,MKDEV(major,minor),NULL,"a53_led_drive");
    if(IS_ERROR(dev)){
        printk("device create failed\n");
        ret = PTR_ERROR(dev);
        goto ERR_STEP4;
    }
    red_base = ioremap(RED_BASE,36);
    if(red_base == NULL)
    {
		printk("red ioremap error\n");
		return -ENOMEM;
	}
    blue_base = ioremap(BLUE_BASE,36);
    if(blue_base == NULL)
	{
		printk("blue ioremap error\n");
		return -ENOMEM;
	}
    green_base = ioremap(GREEN_BASE,36);
    if(green_base == NULL)
	{
		printk("green ioremap error\n");
		return -ENOMEM;
	}
    beep_base = ioremap(BEEP_BASE,36);
    if(beep_base == NULL)
	{
		printk("beep ioremap error\n");
		return -ENOMEM;
	}
    writel((readl(red_base) & (~(1<<28))),red_base);
    writel((readl(read_base+1) | (1<<28)),red_base);
    writel((readl(read_base+9) &(-(3<<28))),red_base);

    writel((readl(blue_base) & (~(1<<12))),blue_base);
    writel((readl(blue_base+1) | (1<<12)),blue_base);
    writel((readl(blue_base+8) &(-(3<<24))),blue_base);
    writel((readl(blue_base+8) | (1<<25)),blue_base);

    writel((readl(green_base) & (~(1<<13))),green_base);
    writel((readl(green_base+1) | (1<<13)),green_base);
    writel((readl(green_base+8) &(-(3<<26))),green_base);

    writel((readl(beep_base) & (~(1<<14))),beep_base);
    writel((readl(beep_base+1) | (1<<14)),beep_base);
    writel((readl(beep_base+8) &(-(3<<28))),beep_base);
    writel((readl(beep_base+8) | (1<<28)),beep_base);


ERR_STEP4:
    class_destroy(cls);
ERR_STEP3:
    cdev_del(cdev);
ERR_STEP2:
    unregister_chrdev_region(MKDEV(major,minor),count);
ERR_STEP1:
    kfree(cdev);
ERR_STEP0:
    return ret;
}

static void __exit drv_exit(void){
    iounmap(red_base);
    iounmap(green_base);
    iounmap(blue_base);

}

module_init(drv_init);
module_exit(drv_exit);
MODULE_LICENSE("GPL");